#ifndef A8_UDPLISTENER_H
#define A8_UDPLISTENER_H

#include <a8/netbase.hpp>

namespace a8
{
	class UdpListener
	{
	public:
		a8::Delegate<void (a8::UdpListener*, int errorId)> OnError;
		std::string mBindAddress;
		unsigned short mBindPort;

		UdpListener():
			mListenSocket(INVALID_SOCKET), mBindPort(0), mWorkerThread(NULL)
		{
		}

		virtual ~UdpListener()
		{
		}

		virtual void OnSocketRead(char*, unsigned int){}
		
		void Open()
		{
			if(!isActive())
				setActive(true);
		}
	 
		void Close()
		{
			if(isActive())
				setActive(false);
		}
	 
		bool isActive() 
		{
			return mListenSocket != INVALID_SOCKET;
		}

	private:
		SOCKET mListenSocket;
		a8::Runnable* mWorkerThread;

		void setActive(bool active) 
		{ 
			if(active){
				if(isActive()) 
					return;
				if(!mWorkerThread){
					mWorkerThread = new a8::Runnable();
					mWorkerThread->OnExecute += std::make_pair(this, &UdpListener::WorkerThreadProc);
					mWorkerThread->Start();
				}
			}else{
				if(isActive())
					ActiveStop();		
			}
		}

		bool ActiveStart()
		{
			mListenSocket = socket(AF_INET, SOCK_DGRAM, 0);
			if(INVALID_SOCKET == mListenSocket){
				OnError(this, errno);
				return false;
			}
			// TIME_WAIT - argh
			int on = 1;
			if (setsockopt(mListenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) == -1 ){
				//return false;
			}
			sockaddr_in sa;
			memset(&sa, 0, sizeof(sa));
			sa.sin_family = AF_INET;
			sa.sin_addr.s_addr = inet_addr(mBindAddress.c_str());
			sa.sin_port = htons(mBindPort);
			if(::bind(mListenSocket, (sockaddr*)&sa, sizeof(sa)) < 0){
				OnError(this, errno);
				closesocket(mListenSocket);
				mListenSocket = INVALID_SOCKET;
				return false;
			}
			return true;
		}

		void ActiveStop()
		{
			if(mListenSocket != INVALID_SOCKET){
				closesocket(mListenSocket);
				mListenSocket = INVALID_SOCKET;								
			}
			if (mWorkerThread){
				delete mWorkerThread;
				mWorkerThread = NULL;
			}
		}

		void WorkerThreadProc(a8::Runnable* thread)
		{
			if(!ActiveStart())
				return;
			struct sockaddr_in destAddr;
			socklen_t addrLen = sizeof(sockaddr_in);
			char recvBuf[1024 * 64];						
			int buflen = sizeof(recvBuf);
			while(!thread->IsShutdown()){
				int ret = ::recvfrom(mListenSocket, recvBuf, buflen, 0, 
									 (struct sockaddr*)&destAddr, (socklen_t*)&addrLen);
				if(ret > 0){
					OnSocketRead(recvBuf, ret);
				}
			}
		}

	};
}

#endif
